package com.sojson.project.sys.user.service.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sojson.base.impl.BaseListDto;
import com.sojson.base.impl.BaseUser;
import com.sojson.config.exception.TransErrorCode;
import com.sojson.config.mybatis.annotation.DataScope;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantField;
import com.sojson.constant.DataScopeType;
import com.sojson.enums.EEnvironmentUserType;
import com.sojson.enums.EUserType;
import com.sojson.enums.core.EUserStatus;
import com.sojson.project.sys.menu.service.MenuService;
import com.sojson.project.sys.role.entity.dto.UserDeptDto;
import com.sojson.project.sys.role.entity.dto.UserRoleDto;
import com.sojson.project.sys.role.entity.po.UserDept;
import com.sojson.project.sys.role.entity.po.UserRole;
import com.sojson.project.sys.role.entity.vo.RoleVo;
import com.sojson.project.sys.role.service.RoleService;
import com.sojson.project.sys.role.service.UserDeptService;
import com.sojson.project.sys.role.service.UserRoleService;
import com.sojson.project.sys.user.dao.UserAdminDao;
import com.sojson.project.sys.user.entity.bo.AuthcUser;
import com.sojson.project.sys.user.entity.dto.UserAdminDto;
import com.sojson.project.sys.user.entity.dto.UserAdminPageDto;
import com.sojson.project.sys.user.entity.po.UserAdmin;
import com.sojson.project.sys.user.entity.vo.UserAdminVo;
import com.sojson.project.sys.user.service.UserAdminService;
import com.sojson.util.BeanUtil;
import com.sojson.util.ExpUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.enums.ELoginOtherType;
import com.sojson.util.file.constant.ConstantFileFieldName;
import com.sojson.util.file.depot.FileDepotUtil;
import com.sojson.util.file.enums.EFileTable;
import com.sojson.util.file.enums.EFileTableBase;
import com.sojson.util.param.ParamUtil;
import com.sojson.util.pwd.PwdUtil;
import com.sojson.util.token.Token;
import com.sojson.util.token.TokenDto;
import com.sojson.util.token.TokenUtil;
import com.sojson.util.token.interfaces.BaseUserService;

/**
 * 管理员用户表Service实现类
 * 
 * @author liu
 * @date 2022-12-05
 */
@Service
public class UserAdminServiceImpl extends ServiceImpl<UserAdminDao, UserAdmin>
    implements UserAdminService, BaseUserService {

    @Lazy
    @Autowired
    private UserAdminServiceImpl userAdminService;
    @Autowired
    private UserServiceImpl userService;
    @Autowired
    private UserMerchantServiceImpl userMerchantService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuService menuService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private UserDeptService userDeptService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Resource
    private UserAdminDao userAdminDao;

    /**
     * 获取当前用户的Service
     * 
     * @return
     * @throws IOException 
     */
    @Override
    public BaseUserService getUserService() throws IOException {
        String env = TokenUtil.getToken().getLoginEnv();
        EEnvironmentUserType[] values = EEnvironmentUserType.values();
        for (EEnvironmentUserType envUserType : values) {
            if (envUserType.getCode().equals(env)) {
                if (EUserType.ADMIN.getCode().equals(envUserType.getType())) {
                    return userAdminService;
                } else if (EUserType.MERCHANT.getCode().equals(envUserType.getType())) {
                    return userMerchantService;
                } else if (EUserType.USER.getCode().equals(envUserType.getType())) {
                    return userService;
                }
                break;
            }
        }

        ExpUtil.throwEx("用户环境不存在");
        return null;
    }

    /**
     * 判断用户是否有操作权限
     * 
     * @param userIds
     * @return
     * @throws IOException 
     */
    @Override
    public boolean isPerm(List<String> userIds) throws IOException {
        if (ParamUtil.isAdminSuper()) {
            return true;
        }

        // 判断是否有查询权限
        ParamUtil.verifyDept(userAdminService.selectDeptIdById(userIds));
        Set<String> roleIdsExist = TokenUtil.getRoleIdsExist();

        for (String id : userIds) {
            // 获取当前用户的角色ID列表
            Set<String> roleIds = roleService.getRoleIdsByUserId(id);
            boolean isRole = false;
            for (String roleId : roleIds) {
                if (roleIdsExist.contains(roleId)) {
                    isRole = true;
                    break;
                }
            }
            if (!isRole) {
                ExpUtil.throwEx(TransErrorCode.NOT_ROLE_PERMISSION);
            }
        }
        return false;
    }

    /**
     * 修改用户头像
     * 
     * @param headImg
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean updateHeadImg(String headImg) throws IOException {
        String id = TokenUtil.getUserId();
        // 旧数据
        BaseUser findById = userAdminDao.findById(id);

        // 修改数据
        UserAdmin dto = new UserAdmin();
        dto.setId(id);
        dto.setHeadImg(headImg);
        int updateById = userAdminDao.updateById(dto);

        if (updateById > 0) {
            FileDepotUtil.updateSync(EFileTable.USER_ADMIN.getCode(), ConstantFileFieldName.HEAD_IMG, headImg,
                findById.getHeadImg());
        }

        // 更新缓存
        TokenUtil.refreshTokenUser(userAdminDao.findById(TokenUtil.getUserId()));
        return false;
    }

    /**
     * 通过id更新个人信息
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateInfo(BaseListDto dto) throws IOException {
        UserAdmin user = new UserAdmin();
        BeanUtil.copyBeanProp(user, dto);
        // 参数初始化
        user.updateProfileInit();

        String id = user.getId();
        UserAdminVo findById = userAdminDao.findById(id);
        if (findById == null) {
            ExpUtil.throwEx("用户不存在!");
        }

        String mobile = user.getMobile();
        List<UserAdminVo> findByMobile = userAdminDao.findByMobile(mobile);
        if (StringUtil.isNotBlankObject(findByMobile)) {
            int size = findByMobile.size();
            if (size > 1) {
                ExpUtil.throwEx("手机号已存在！");
            } else if (size == 1 && !findByMobile.get(0).getId().equals(id)) {
                ExpUtil.throwEx("手机号已存在！");
            }
        }

        // 修改用户
        userAdminDao.updateById(user);

        // 更新缓存
        TokenUtil.refreshTokenUser(userAdminDao.findById(TokenUtil.getUserId()));
        return true;
    }

    /**
     * 通过id验证旧密码修改密码
     * 
     * @param dto
     * @return
     * @throws Exception 
     */
    @Override
    public boolean updatePwdByOldPwd(AuthcUser dto) throws Exception {
        String id = dto.getId();
        UserAdminVo findById = userAdminDao.findById(id);
        // 查看旧密码是否输入正确
        // 判断密码是否正确
        if (!PwdUtil.matchesPwd(dto.getOldPwd(), findById.getPwd())) {
            ExpUtil.throwEx(TransErrorCode.ERR_OLD_PWD);
        }

        UserAdmin user = new UserAdmin();
        user.setId(id);
        user.setPwd(Constant.getUserPassword(dto.getPwd()));
        userAdminDao.updateById(user);
        return true;
    }

    /**
     * 帐号密码注册
     * 
     * @param dto
     * @return
     * @throws Exception 
     */
    @Override
    public boolean pwdRegister(TokenDto dto) throws Exception {
        BaseUser baseUser = dto.getUser();
        UserAdminVo user = new UserAdminVo();
        BeanUtil.copyBeanProp(user, baseUser);
        dto.setUser(user);

        // 插入初始化
        user.insertInit(dto);

        return userAdminService.pwdRegisterGo(dto);
    }

    @Transactional(rollbackFor = Throwable.class)
    public boolean pwdRegisterGo(TokenDto dto) throws Exception {
        UserAdminVo user = (UserAdminVo)dto.getUser();

        // 检查手机号是否存在
        String mobile = user.getMobile();
        if (StringUtil.isNotBlankObject(mobile)) {
            int countByMobile = userAdminDao.countByMobile(user.getMobile());
            // 如果存在就抛异常
            if (countByMobile > 0) {
                ExpUtil.throwEx("手机号已存在");
            }
        }
        int insert = 0;
        // 查看此帐号是否存在
        int countByUsername = userAdminDao.countByUsername(user.getUsername());
        // 如果存在就抛异常
        if (countByUsername > 0) {
            ExpUtil.throwEx(TransErrorCode.EXIST_USER);
        }

        // 处理其他业务
        FileDepotUtil.insertSync(EFileTableBase.USER.getCode(), ConstantFileFieldName.HEAD_IMG, user.getHeadImg());

        // 返回结果
        insert = userAdminDao.insert(user);
        if (insert < 1) {
            ExpUtil.throwEx("注册用户失败！");
        }

        // 添加角色
        Set<String> roleIds = dto.getRoleIds();
        if (roleIds != null && roleIds.size() > 0) {
            List<UserRole> arrayList = new ArrayList<>();
            for (String roleId : roleIds) {
                UserRole userRole = new UserRole();
                arrayList.add(userRole);
                userRole.setUserId(user.getId());
                userRole.setRoleId(roleId);
            }

            boolean saveBatch = userRoleService.saveBatch(arrayList);
            if (!saveBatch) {
                ExpUtil.throwEx("添加用户角色失败");
            }
        }

        // 添加部门
        if (user.getDataScope() == DataScopeType.DATA_SCOPE_CUSTOM) {
            Set<String> deptIds = dto.getDepts();
            if (deptIds != null && deptIds.size() > 0) {
                List<UserDept> arrayList = new ArrayList<>();
                for (String dId : deptIds) {
                    UserDept userDept = new UserDept();
                    arrayList.add(userDept);
                    userDept.setUserId(user.getId());
                    userDept.setDeptId(dId);
                }

                boolean saveBatch = userDeptService.saveBatch(arrayList);
                if (!saveBatch) {
                    ExpUtil.throwEx("添加用户部门失败");
                }
            }
        }

        return insert > 0;
    }

    /**
     * 密码登录
     * 
     * @param dto
     * @return
     * @throws Exception 
     */
    @Override
    public Token pwdLogin(TokenDto dto) throws Exception {
        return submitLogin(dto);
    }

    /**
    * 登录
    *
    * @param dto
    * @return
    * @throws Exception
    */
    public Token submitLogin(TokenDto dto) throws Exception {
        // 用户信息
        BaseUser user = dto.getUser();
        // 帐号
        String username = null;
        if (ELoginOtherType.GENERAL.getCode() == dto.getLoginType()) {
            username = user.getUsername();
        } else if (ELoginOtherType.MOBILE.getCode() == dto.getLoginType()) {
            username = user.getMobile();
        } else {
            ExpUtil.throwEx(TransErrorCode.NOT_EXIST_USER_TYPE);
        }

        // 保存参数
        TokenUtil.setLoginParam(dto);

        // 用户验证
        // 该方法会去调用UserDetailsService.loadUserByUsername
        Authentication authenticate =
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, user.getPwd()));
        Token token = (Token)authenticate.getPrincipal();

        return token;
    }

    /**
     * 通过登录信息获取用户
     * 
     * @param dto
     * @param username
     * @return
     */
    @Override
    public BaseUser loadUserByUsername(TokenDto dto, String username) {
        // 获取参数
        TokenDto param = TokenUtil.getLoginParam();

        // 获取用户
        BaseUser user = null;
        if (ELoginOtherType.GENERAL.getCode() == param.getLoginType()) {
            user = findByUsername(username);
            if (StringUtil.isBlankObject(user)) {
                ExpUtil.throwEx(TransErrorCode.NOT_USER);
            }
            // 判断密码是否正确
            if (!PwdUtil.matchesPwd(param.getPassword(), user.getPwd())) {
                ExpUtil.throwEx(TransErrorCode.ERR_PWD);
            }
        }
        return user;
    }

    /**
     * 通过id更新
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean update(UserAdminDto dto) throws IOException {
        UserAdmin user = new UserAdmin();
        BeanUtil.copyBeanProp(user, dto);
        // 参数初始化
        user.updateInit();

        String id = user.getId();
        UserAdminVo findById = userAdminDao.findById(id);
        if (findById == null) {
            ExpUtil.throwEx("用户不存在!");
        }

        String mobile = user.getMobile();
        List<UserAdminVo> findByMobile = userAdminDao.findByMobile(mobile);
        if (StringUtil.isNotBlankObject(findByMobile)) {
            int size = findByMobile.size();
            if (size > 1) {
                ExpUtil.throwEx("手机号已存在！");
            } else if (size == 1 && !findByMobile.get(0).getId().equals(id)) {
                ExpUtil.throwEx("手机号已存在！");
            }
        }

        // 超级管理员不能禁用
        Integer status = user.getStatus();
        List<RoleVo> getRole = roleService.getRolesByUserId(id);
        boolean adminSuper = ParamUtil.isAdminSuper(getRole);
        if (StringUtil.isNotBlankObject(status) && status == EUserStatus.DISABLE.getCode()) {
            if (adminSuper) {
                ExpUtil.throwEx("超级管理员不能禁用");
            } else if (id == TokenUtil.getUserId()) {
                ExpUtil.throwEx("不能禁用自己");
            }
        }

        // 超级管理员不能修改部门权限
        if (adminSuper) {
            user.setDataScope(null);
            user.setDeptId(null);
        }

        // 修改用户
        userAdminDao.updateById(user);

        // 如果不是超级管理员就修改角色和部门
        if (!adminSuper) {
            Set<String> roleIds = dto.getRoleIds();
            // 将没有权限修改的角色保留
            List<String> listIds = getRole.stream().map(RoleVo::getId).collect(Collectors.toList());
            for (String roleId : listIds) {
                if (!ParamUtil.isRoleIdExist(roleId)) {
                    roleIds.add(roleId);
                }
            }
            // 删除原有角色
            UserRoleDto userRoleDto = new UserRoleDto();
            userRoleDto.setUserId(id);
            userRoleService.removeByDto(userRoleDto);

            // 添加角色
            if (roleIds.size() > 0) {
                // 新增角色
                List<UserRole> arrayList = new ArrayList<>();
                for (String roleId : roleIds) {
                    UserRole userRole = new UserRole();
                    arrayList.add(userRole);
                    userRole.setUserId(id);
                    userRole.setRoleId(roleId);
                }
                boolean saveBatch = userRoleService.saveBatch(arrayList);
                if (!saveBatch) {
                    ExpUtil.throwEx("添加用户角色失败");
                }
            }

            Integer dataScope = dto.getDataScope();
            if (dataScope != null) {
                // 清空部门
                UserDeptDto userDeptDto = new UserDeptDto();
                userDeptDto.setUserId(id);
                userDeptService.removeByDto(userDeptDto);
                if (dataScope == DataScopeType.DATA_SCOPE_CUSTOM) {
                    Set<String> deptIds = dto.getDeptIds();

                    // 添加部门
                    if (deptIds.size() > 0) {
                        // 新增部门
                        List<UserDept> arrayList = new ArrayList<>();
                        for (String dId : deptIds) {
                            UserDept userDept = new UserDept();
                            arrayList.add(userDept);
                            userDept.setUserId(id);
                            userDept.setDeptId(dId);
                        }
                        boolean saveBatch = userDeptService.saveBatch(arrayList);
                        if (!saveBatch) {
                            ExpUtil.throwEx("添加用户部门失败");
                        }
                    }
                }
            }
        }

        // 踢出此账号的所有Token
        TokenUtil.logout(findById.getId());
        return true;
    }

    /**
     * 通过对象查找
     * 
     * @param dto
     * @return
     */
    @Override
    @DataScope(deptAlias = "u", deptField = ConstantField.DEPT_ID, userAlias = "u", userField = ConstantField.ID,
        roleIsAll = true, roleAlias = "ur", roleField = ConstantField.ROLE_ID)
    public List<UserAdminVo> list(UserAdminDto dto) {
        List<UserAdminVo> list = userAdminDao.list(dto);
        return list;
    }

    /**
     * 通过对象分页查找
     * 
     * @param dto
     * @return
     */
    @Override
    @DataScope(deptAlias = "u", deptField = ConstantField.DEPT_ID, userAlias = "u", userField = ConstantField.ID,
        roleIsAll = true, roleAlias = "ur", roleField = ConstantField.ROLE_ID)
    public List<UserAdminVo> page(UserAdminPageDto dto) {
        List<UserAdminVo> page = userAdminDao.page(dto);
        return page;
    }

    /**
     * 通过id查找
     * 
     * @param id
     * @return
     */
    @Override
    public UserAdminVo findById(String id) {
        UserAdminVo findById = userAdminDao.findById(id);
        return findById;
    }

    /**
     * 通过对象删除
     * 
     * @param dto
     * @return
     */
    @Override
    public boolean removeByDto(UserAdminDto dto) {
        userAdminDao.removeByDto(dto);
        return true;
    }

    /**
     * 清空数据
     * 
     * @return
     */
    @Override
    public boolean clear() {
        userAdminDao.clear();
        return true;
    }

    /**
     * 密码重置
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    public boolean resetUserPwd(UserAdmin dto) throws IOException {
        String pwd = dto.getPwd();
        if (StringUtil.isBlankObject(pwd)) {
            pwd = Constant.getUserPassword();
        } else {
            pwd = Constant.getUserPassword(pwd);
        }

        dto.setPwd(pwd);
        userAdminDao.updateById(dto);

        TokenUtil.logout(dto.getId());
        return true;
    }

    /**
     * 通过id列表删除
     * 
     * @param ids
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(List<String> ids) throws IOException {
        if (menuService.getAdminSuperRoleNumByUserIds(ids) > 0) {
            ExpUtil.throwEx("超级管理员不能删除");
        }

        // 删除用户
        userAdminDao.deleteBatchIds(ids);

        // 删除用户关联的角色
        userRoleService.deleteByUserIds(new HashSet<>(ids));

        // 删除用户关联的部门
        userDeptService.deleteByUserIds(new HashSet<>(ids));

        TokenUtil.logout(ids);
        return true;
    }

    /**
     * 通过id修改状态
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStatus(UserAdmin dto) throws IOException {
        String id = dto.getId();
        Integer status = dto.getStatus();
        if (ParamUtil.isAdminSuper(roleService.getRolesByUserId(id))) {
            ExpUtil.throwEx("超级管理员不能修改状态！");
        }

        userAdminDao.updateById(dto);

        // 如果禁用就踢出此账号的所有Token
        if (status == EUserStatus.DISABLE.getCode()) {
            UserAdminVo findById = userAdminDao.findById(id);
            if (StringUtil.isNotBlankObject(findById)) {
                TokenUtil.logout(findById.getId());
            }
        }
        return true;
    }

    /**
     * 通过帐号查询
     * 
     * @param username
     * @return
     */
    @Override
    public UserAdminVo findByUsername(String username) {
        return userAdminDao.findByUsername(username);
    }

    /**
     * 通过手机号查询
     * 
     * @param mobile    
     * @return
     */
    @Override
    public List<UserAdminVo> findByMobile(String mobile) {
        return userAdminDao.findByMobile(mobile);
    }

    /**
     * 通过用户ID查询部门ID
     * 
     * @param ids
     * @return
     */
    @Override
    public Set<String> selectDeptIdById(List<String> ids) {
        return userAdminDao.selectDeptIdById(ids);
    }

}